package nsqlookupd

import (
	"fmt"
	"log"
	"net"
	"os"
	"sync"

	"github.com/nsqio/nsq/internal/http_api"
	"github.com/nsqio/nsq/internal/protocol"
	"github.com/nsqio/nsq/internal/util"
	"github.com/nsqio/nsq/internal/version"
)

type NSQLookupd struct {
	//读写互斥锁 https://golang.org/pkg/sync/#RWMutex
	//http://blog.csdn.net/aslackers/article/details/62044726
	/**
	 * 基本遵循两大原则：
	 * 1、可以随便度，多个goroutine同时读
	 * 2、写的时候，啥也不能干。不能读也不能写
	 */
	sync.RWMutex
	//在文件nsqlookupd/options.go中定义，记录NSQLookupd的配置信息
	opts         *Options
	tcpListener  net.Listener
	httpListener net.Listener
	//在文件internal/util/wait_group_wrapper.go中定义，与sync.WaitGroup相关，用于线程同步
	waitGroup util.WaitGroupWrapper
	//在文件nsqlookupd/registration_db.go中定义，与数据存储有关
	DB *RegistrationDB
}

//根据配置的nsqlookupd options信息，创建一个NSQLookupd实例
func New(opts *Options) *NSQLookupd {
	if opts.Logger == nil {
		opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds)
	}
	n := &NSQLookupd{
		opts: opts,
		DB:   NewRegistrationDB(),
	}
	n.logf(version.String("nsqlookupd"))
	return n
}

func (l *NSQLookupd) logf(f string, args ...interface{}) {
	l.opts.Logger.Output(2, fmt.Sprintf(f, args...))
}

//Main函数，启动nsqlockupd进程时，
//首先运行nsqlookupd/options.go里的NewOptions()这个方法，设置默认配置
//然后运行上面的New()方法，根据配置信息创建一个NSQLookupd实例
//再运行这里的Main()方法，启动nsqlockupd进程
//详情见apps/nsqlookupd/nsqlookupd.go里的Start()方法
func (l *NSQLookupd) Main() {
	//Context实例，在文件nsqlookupd/context.go中定义，NSQLookupd类型的指针
	ctx := &Context{l}

	//监听TCP，监听地址取options中的默认参数，0.0.0.0:4160
	tcpListener, err := net.Listen("tcp", l.opts.TCPAddress)
	if err != nil {
		l.logf("FATAL: listen (%s) failed - %s", l.opts.TCPAddress, err)
		os.Exit(1)
	}
	//RWMutex写锁
	l.Lock()
	//将Listener保存到NSQLookupd对象元素里，供后面的方法调用，如：RealTCPAddr()
	l.tcpListener = tcpListener
	l.Unlock()
	//tcpServer实例，在文件nsqlookupd/tcp.go中定义，处理TCP接收到的数据
	tcpServer := &tcpServer{ctx: ctx}
	//使用sync.WaitGroup，关于sync.WaitGroup介绍，http://blog.csdn.net/aslackers/article/details/62046306
	//通过下面的Exit()方法，可知，当关闭nsqlookupd进程时，主线程(goroutine)会等待所有TCP监听关闭，才关闭自己
	l.waitGroup.Wrap(func() {
		//在文件internal/protocol/tcp_server.go中定义，
		//接收监听并处理TCP数据
		//第二个参数tcpServer实现了internal/protocol/tcp_server.go文件中定义的TCPHandler接口
		//tcpServer接收到TCP数据时，会调用其Handle()方法(nsqlookupd/tcp.go)处理。
		//核心逻辑，高度封装后，对外呈现的只是一行代码
		//处理nsqd发送的消息
		protocol.TCPServer(tcpListener, tcpServer, l.opts.Logger)
	})

	//监听并处理HTTP，逻辑和上面TCP差不多 0.0.0.0:4161
	httpListener, err := net.Listen("tcp", l.opts.HTTPAddress)
	if err != nil {
		l.logf("FATAL: listen (%s) failed - %s", l.opts.HTTPAddress, err)
		os.Exit(1)
	}
	l.Lock()
	l.httpListener = httpListener
	l.Unlock()
	//httpServer实例，在文件nsqlookupd/http.go中定义，处理HTTP路由分发
	//并定义了一系列HTTP接口(路由)
	httpServer := newHTTPServer(ctx)
	l.waitGroup.Wrap(func() {
		//核心逻辑
		http_api.Serve(httpListener, httpServer, "HTTP", l.opts.Logger)
	})
}

//获取监听的TCP地址
func (l *NSQLookupd) RealTCPAddr() *net.TCPAddr {
	l.RLock()
	defer l.RUnlock()
	return l.tcpListener.Addr().(*net.TCPAddr)
}

//获取HTTP地址
func (l *NSQLookupd) RealHTTPAddr() *net.TCPAddr {
	l.RLock()
	defer l.RUnlock()
	return l.httpListener.Addr().(*net.TCPAddr)
}

//退出nsqloopupd进程，关闭两个Listener，等待TCP和HTTP线程都关闭了，才关闭自身
func (l *NSQLookupd) Exit() {
	if l.tcpListener != nil {
		l.tcpListener.Close()
	}

	if l.httpListener != nil {
		l.httpListener.Close()
	}
	l.waitGroup.Wait()
}

//1、执行nsqlookupd/options.go中的NewOptions()方法，创建Options实例，生成NSQLookupd配置信息
//2、执行上面的New()方法，将第1步中创建的Options配置信息作为参数，创建NSQLookupd实例
//3、执行NSQLookupd实例的Main()方法，创建nsqloopupd进程，TCP监听0.0.0.0:4160，HTTP监听0.0.0.0:4161
//nsqloopupd进程退出时，调用NSQLookupd实例的Exit()方法，关闭TCP和HTTP监听，主线程(goroutine)等待子线程(goroutine)退出，程序退出